#include <stdio.h>
#include <stdlib.h>
#include <BlockSorting.h>
#include <DataInputOutput.h>
#include <Logging.h>
#include <assert.h>

namespace processes {
using namespace logging;

unsigned char buffer[BS_BUFFER_SIZE];
int indices[BS_BUFFER_SIZE];
int length;
unsigned int T[BS_BUFFER_SIZE];
unsigned int cnt[256];
unsigned int cntTotal[256];

/*
 * Funcion de comparación que ordena el array de suffijos.
 * 
 * Recibe puntero al array de indicies que se usan para obtener y comparar los
 * sufijos.
 */
int blocksorting_cmp( const unsigned int *i1, const unsigned int *i2 ) {
    unsigned int l1 = (unsigned int) ( length - *i1 );
    unsigned int l2 = (unsigned int) ( length - *i2 );
    // priorizo el uso de registros en lugar de stack para estas operaciones
    // aunque el compilador podria usarlos de cualquier manera
    register unsigned char *str1 = buffer + *i1, *str2 = buffer + *i2;
    register int result = 0, len = 0;    
    
    // Uso una implementacion mejor que memcmp.
    // memcmp puede tener comportamientos distintos (signed/unsigned) y
    // implementaciones poco optimizadas
    
    // result = memcmp(buffer + *i1, buffer + *i2, l1 < l2 ? l1 : l2 );
  
    len = length;
    result = 0;
    while (len-- && result == 0) {
      if (str2 - buffer >= length) {
        str2 = buffer;
      }
      if (str1 - buffer >= length) {
        str1 = buffer;
      }
      result = *str1++ - (int)*str2++;
    }
    
    return result;
};

BlockSortingForwardTransformationProcess::BlockSortingForwardTransformationProcess(){
    
}

list<DataUnit*> BlockSortingForwardTransformationProcess::process(const list<DataUnit*> dataUnits){
    Logger& logger = LoggerFactory::getInstance().getLogger("processes.bs");
    logger.log(logging::DEBUG, "Starting BS .");    
    
    if ((length = dataUnits.size()) > sizeof(buffer)) {
        // exception
        logger.log(logging::ERROR, "Internal block bigger than expected.");
    }
    
    assert(dataUnits.size() <= sizeof(buffer));
    
    list<DataUnit*>::const_iterator it = dataUnits.begin();
    for ( int i = 0; it != dataUnits.end(); it++, i++) {
        buffer[i] = (*it)->asInt();
        indices[i] = i;
    }
    
    qsort(  
        indices, 
        (int)( length ), 
        sizeof( int ), 
        ( int (*)(const void *, const void *) ) blocksorting_cmp 
    );  
   
    list<DataUnit*> newDataUnitsList;
    
    // agrego el entero como chars a la lista
    for ( int i = 0 ; i < sizeof(int) ; i++ ) {
        newDataUnitsList.push_back(new CharDataUnit(((unsigned char *)&length)[i]));
    }
    
    int first;
    for ( int i = 0 ; i < length ; i++ ) {
        if ( indices[i] == 0 ) {
            // guardo el primero
            first = i;
            // mando el ultimo caracter del bloque
            newDataUnitsList.push_back(new CharDataUnit(buffer[length - 1]));
        } 
        else {
            newDataUnitsList.push_back(new CharDataUnit(buffer[ indices[i] - 1]));
        }
    }
      
    // agrego el entero como chars a la lista
    for ( int i = 0 ; i < sizeof(int) ; i++ ) {
        newDataUnitsList.push_back(new CharDataUnit(((unsigned char *)&first)[i]));
    }    
    
    return newDataUnitsList;
}


BlockSortingForwardTransformationProcess::~BlockSortingForwardTransformationProcess(){

}

BlockSortingBackwardTransformationProcess::BlockSortingBackwardTransformationProcess(){

}

list<DataUnit*> BlockSortingBackwardTransformationProcess::process(const list<DataUnit*> dataUnits){
    Logger& logger = LoggerFactory::getInstance().getLogger("processes.bs");
    logger.log(logging::DEBUG, "Starting des-BS .");    
    
    int sum = 0;
    unsigned int first;
    unsigned int i, j;
    
    list<DataUnit*>::const_iterator it = dataUnits.begin();
    
    // recupero el BS length
    for ( int i = 0 ; i < sizeof(int) ; it++, i++) {
        ((unsigned char *)&length)[i] = (*it)->asInt();
    }
    
    assert(dataUnits.size() == length+2*sizeof(int));
    assert(length <= sizeof(buffer));
    
    // recupero el BS en el buffer para procesar
    for ( int i = 0; i < length; it++, i++) {
        buffer[i] = (*it)->asInt();
    }
    
    // recupero el BS first
    for ( int i = 0 ; i < sizeof(int) ; it++, i++) {
        ((unsigned char *)&first)[i] = (*it)->asInt();
    }
    
    //   
    // Los indicies del vector T los construyo a partir de F, y para conocer F,
    // que es el vector ordenado de L, lo puedo hacer conociendo cuantos caracteres
    // alfabeticamente anteriores hay previos a cada cada caracter y luego,
    // al recorrer L, ver que instancia de cada caracter estoy recorriendo.
    
    // Entonces primero cuento la cantidad total de cada caracter
    // inicializo los contadores en cero
    for (i=0 ; i < 256 ; i++) {
      cnt[i] = 0;
    }
    for (i=0 ; i<length ; i++) {
        cnt[buffer[i]]++;
    }

    sum = 0;
    for (i=0 ; i< 256 ; i++) {
        cntTotal[ i ] = sum;
        sum += cnt[i];

        // lo vuelvo a inicializar en 0 para contar las intancias 
        // mientras construyo el vector T
        cnt[i] = 0;
    }
    
    // ahora voy consturyendo el vector T con la suma de lo anterior mas
    // la instancia de cada caracter (cantidad de cada carecter recorridos
    // hasta el momento)
    for (i = 0; i < length ; i++) {
        T[cnt[buffer[i]] + cntTotal[buffer[i]]] = i;
        cnt[buffer[i]]++;
    }
    
    list<DataUnit*> newDataUnitsList;   
    
    // Con el vector T, ya puedo recuperar la informacion original
    i = T[first];
    for ( j = 0 ; j < length ; j++) {
        newDataUnitsList.push_back(new CharDataUnit(buffer[i]));
        i = T[i];
    }    
    
    return newDataUnitsList;
}

BlockSortingBackwardTransformationProcess::~BlockSortingBackwardTransformationProcess(){

}

}
